package com.hc.wego.utils;

import com.hc.wego.domain.entity.Advertise;
import com.hc.wego.domain.vo.UserVO;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 *
 * @author hc
 * @date 2025-06-18 10:29
 */
public class ReflectUtil {
    /**
     * 获取指定类及其所有父类中声明的字段（不包括 Object 类中的字段）
     *
     * @param clazz 要获取字段的类
     * @return 包含所有字段的列表
     */
    private static List<Field> getAllFields(Class<?> clazz) {
        if (clazz == null) {
            throw new IllegalArgumentException("字节码不能为空");
        }

        final List<Field> res = new ArrayList<>();
        while (clazz != Object.class) {
            final Field[] fields = clazz.getDeclaredFields();
            if (fields.length > 0) {
                Collections.addAll(res, fields);
            }
            clazz = clazz.getSuperclass();
        }
        return res;
    }



    /**
     * 查询出实体类字节码对应数据库中所有的字段名称的列表
     *
     * @param clazz 要处理的类如果传入null，将抛出IllegalArgumentException异常
     * @return 包含所有字段名（转换为蛇形命名法）的列表
     * @throws IllegalArgumentException 如果传入的类参数为null
     */
    public static List<String> getTableFileName(Class<?> clazz) {
        // 检查传入的类是否为null，如果是，则抛出异常
        if (clazz == null) {
            throw new IllegalArgumentException("参数字节码不能为null");
        }

        // 获取类的所有字段，包括继承的字段
        final List<Field> allFields = getAllFields(clazz);

        if (CollectionUtil.isEmpty(allFields)) {
            return Collections.emptyList();
        }

        // 创建一个列表来存储字段名，初始化容量为字段数量，以减少数组拷贝
        List<String> fieldNameList = new ArrayList<>(allFields.size());

        // 遍历所有字段
        for (Field field : allFields) {
            // 将字段名从驼峰命名法转换为蛇形命名法
            String fieldName = StringUtil.hump2underline(field.getName());

            // 将转换后的字段名添加到列表中
            fieldNameList.add(fieldName);
        }

        // 返回包含所有字段名的列表
        return fieldNameList;
    }


    /**
     * 根据给定的类和要排除的属性名称，查询出实体类字节码对应数据库中、排除指定字段的、字段名称的列表
     * 这个方法首先会检查类是否为null，如果是，则抛出IllegalArgumentException异常
     * 如果排除的属性名称为null，则调用重载方法getTableFileName只传入类参数
     * 这个方法的目的是为了提供灵活性，允许调用者指定哪些字段不应该包含在返回的列表中
     *
     * @param clazz 类的字节码，不能为null
     * @param excludePropertyNames 可变参数，指定哪些属性名不应该被包含在返回的列表中，如果为null，则不考虑排除任何属性
     * @return 返回一个字符串列表，包含表的字段名，不包括指定的排除属性
     * @throws IllegalArgumentException 如果类参数为null，则抛出此异常
     */
    public static List<String> getTableFileName(Class<?> clazz, String... excludePropertyNames) {
        // 检查类参数是否为null，如果为null，则抛出IllegalArgumentException异常
        if (clazz == null) {
            throw new IllegalArgumentException("参数字节码不能为null");
        }
        // 如果排除的属性名称为null，则调用重载方法getTableFileName只传入类参数
        if (excludePropertyNames == null) {
            return getTableFileName(clazz);
        }

        // 获取类的所有字段名列表
        final List<String> allFieldNameList = getTableFileName(clazz);
        // 如果所有字段名列表为空，则返回一个空的不可变列表
        if (CollectionUtil.isEmpty(allFieldNameList)) {
            return Collections.emptyList();
        }
        // 保持顺序 + 高效删除
        final Set<String> res = new LinkedHashSet<>();
        for (String fieldName : allFieldNameList) {
            res.add(StringUtil.hump2underline(fieldName));
        }

        // 创建一个不可变的集合，用于存储需要排除的表字段
        final Set<String> tmp = Arrays.stream(excludePropertyNames)
                // 过滤掉excludeFields数组中的null值，确保后续处理的数据有效性
                .filter(Objects::nonNull)
                // 将非null的字符串从驼峰命名转换为下划线命名，以满足特定的命名约定或需求
                .map(StringUtil::hump2underline)
                // 收集转换后的字符串到一个集合中，便于后续的处理或使用
                .collect(Collectors.toSet());
        // 从结果集中移除所有需要排除的字段
        res.removeAll(tmp);

        // 返回最终的字段名列表
        return new ArrayList<>(res);
    }


    public static void main(String[] args) {
        final List<String> list = getTableFileName(UserVO.class);
        list.forEach(System.out::println);

        System.out.println("--------");

        final List<String> list2 = getTableFileName(Advertise.class, "info", "deleted", "updateTime");
        list2.forEach(System.out::println);
    }
}
